using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Media;

using System.Diagnostics;
using VuzixSample.model;


namespace VuzixSample.controller
{
    /// <summary>
    /// This is the main type for your game
    /// 
    /// Gareth Williams
    /// 
    /// A boilerplate project for using Vuzix 920 VR glasses with an XNA4.0 project
    /// Requires the Vuzix SDK:
    /// http://www.vuzix.com/support/developer_program.html
    /// 
    /// Uses (adapted) classes from the GoblinXNA AR library:
    /// http://graphics.cs.columbia.edu/projects/goblin/
    /// - which seem to be tidied up versions of the vuzix SDK sample code :)
    /// 
    /// and also some code from the XNA 3.1 project in the vuzix sdk - mainly for rendering to each eye
    /// 
    /// the 3d cube and shader comes from this tutorial:
    /// http://iloveshaders.blogspot.co.uk/2011/04/drawing-simple-cube.html
    /// 
    /// This is essentially GoblinXNA tutorial 13 
    ///     InputMapper dependency removed - we are dealing direct with a specific piece of hardware
    ///     All Stereo-camera (input) code removed - we are not dealing with 920 AR 
    ///     All marker-card tracking removed - the intention for this code is to display a VR environment
    ///         although you could re-implement the above by using the GoblinXNA framework instead of this bootstrap
    ///     All 'skinned' 3d model code removed - we just use a simple cube
    ///     All scene graph dependencies removed - we render each eye using code from the XNA 3.1 example in the vuzix SDK
    ///         adapted to take advantage of the refactoring in Goblin
    ///     
    ///     Note: Because I have on-board graphics on my laptop, this example compiles for 'reach' profile
    ///        this means that some advanced pipeline features are not available - you can change this for hi-def
    ///        but that is untested so far.
    /// 
    ///     TODO: Refactoring - this class has too many responsibilities
    ///     09/04/2012  - refactored view and projection into camera class
    ///                 - refactored cube model into cube class
    /// 
    ///     TODO: refactor world and cube into a scene
    ///     09/04/2012  - scene requires shader to be injected, probably needs to be encapsulated - more refactoring.
    ///     
    ///     TODO: Game requires scene handling functionality
    ///     
    /// </summary>
    public class Game1 : Microsoft.Xna.Framework.Game
    {
        GraphicsDeviceManager graphics;
        SpriteBatch spriteBatch;
   
        private
            
            iWearTracker iTracker;
            
            bool stereoMode = true;  //stereomode here refers to stereo-output - implemented as side-by-side Stereoscopic
            bool fullScreen = true;

            //lets us exit
            KeyboardState currentKeyboardState;

            //left and right eye sections of the display buffer
            //Viewport defaultVP; //to be used if stereo mode were to be off
            Viewport leftVP;
            Viewport rightVP;
        
            //composition
            Effect simpleColorEffect;
            Camera camera;
            IScene currentScene;

        public Game1()
        {
            graphics = new GraphicsDeviceManager(this);
            Content.RootDirectory = "Content";

            // full screen doesn't seem to display on the 920 VR in 'reach' profile ?
            if (fullScreen)
            {
                graphics.PreferredBackBufferHeight = 768;
                graphics.PreferredBackBufferWidth = 1024;
                graphics.SynchronizeWithVerticalRetrace = true;
                graphics.IsFullScreen = true;
            }
            else
            {
                graphics.PreferredBackBufferWidth = 1280;//2 * 640 * 480 in parallel
                graphics.PreferredBackBufferHeight = 480;
            }
        }

        /// <summary>
        /// Allows the game to perform any initialization it needs to before starting to run.
        /// This is where it can query for any required services and load any non-graphic
        /// related content.  Calling base.Initialize will enumerate through any components
        /// and initialize them as well.
        /// </summary>
        protected override void Initialize()
        {
            base.Initialize();

            // Set up the VUZIX's iWear VR920 for both stereo and orientation tracking
            SetupIWear();

            camera = new Camera();
            camera.Initialise(leftVP);

            currentScene = new Demo();
            currentScene.Initialise(graphics);
        }
        private void SetupIWear()
        {
            // Get an instance of iWearTracker
            iTracker = iWearTracker.Instance;
            
            // We need to initialize it before adding it to the InputMapper class
            iTracker.Initialize(graphics);
            
            //this sets 920 VR output into side-by-side stereo mode
            //and sets up two cameras into the scene - one for each eye
            if (stereoMode)
            {
                iTracker.EnableStereo = true;
                SetupStereoViewport();
            }
        }
        private void SetupStereoViewport()
        {
            leftVP = graphics.GraphicsDevice.Viewport;
            rightVP = graphics.GraphicsDevice.Viewport;

            leftVP.Height = graphics.PreferredBackBufferHeight;
            leftVP.Width = graphics.PreferredBackBufferWidth / 2;
            leftVP.X = 0;
            leftVP.Y = 0;

            rightVP.Height = graphics.PreferredBackBufferHeight;
            rightVP.Width = graphics.PreferredBackBufferWidth / 2;
            rightVP.X = graphics.PreferredBackBufferWidth / 2;
            rightVP.Y = 0;
        }

        /// <summary>
        /// LoadContent will be called once per game and is the place to load
        /// all of your content.
        /// </summary>
        protected override void LoadContent()
        {
            // Create a new SpriteBatch, which can be used to draw textures.
            spriteBatch = new SpriteBatch(GraphicsDevice);

            // TODO: use this.Content to load your game content here
            simpleColorEffect = Content.Load<Effect>("SimpleColor");
            
        }
       
        /// <summary>
        /// UnloadContent will be called once per game and is the place to unload
        /// all content.
        /// </summary>
        protected override void UnloadContent()
        {
            // TODO: Unload any non ContentManager content here
            spriteBatch.Dispose();//not really used but hey
            Content.Dispose();//shader 
            iTracker.Dispose();//tracker (also resets video mode)

            camera.Dispose();
            currentScene.Dispose();
        }

        /// <summary>
        /// Allows the game to run logic such as updating the world,
        /// checking for collisions, gathering input, and playing audio.
        /// </summary>
        /// <param name="gameTime">Provides a snapshot of timing values.</param>
        protected override void Update(GameTime gameTime)
        {
            // Allows the game to exit
            if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed)
                this.Exit();

            currentKeyboardState = Keyboard.GetState();
            if (currentKeyboardState.IsKeyDown(Keys.Escape))
                this.Exit();

            //elapsed game time, whether the window has focus
            iTracker.Update(gameTime.ElapsedGameTime, this.IsActive);
            camera.Rotate(iTracker.WorldTransformation);

            currentScene.Update();

            base.Update(gameTime);
        }

        /// <summary>
        /// This is called when the game should draw itself.
        /// </summary>
        /// <param name="gameTime">Provides a snapshot of timing values.</param>
        protected override void Draw(GameTime gameTime)
        {
            graphics.GraphicsDevice.Clear(Color.Black);

            if (stereoMode) //stereomode refers to having two screens *not cameras*
            {
                ///////////// First, render the scene for the left eye view /////////////////

                //update the lowest scanline incase of windowed mode (window pos might change)
                iTracker.UpdateBottomLine(this, graphics);
                // Begin GPU query for rendering the scene
                iTracker.BeginGPUQuery();
                RenderScene(gameTime, Eyes.LEFT_EYE);
                // Wait for the GPU to finish rendering
                iTracker.EndGPUQuery();
                // Signal the iWear VR920 that the image for the left eye is ready
                iTracker.SynchronizeEye(Eyes.LEFT_EYE, graphics);

                ///////////// Then, render the scene for the right eye view /////////////////

                // Begin GPU query for rendering the scene
                iTracker.BeginGPUQuery();
                RenderScene(gameTime, Eyes.RIGHT_EYE);
                // Wait for the GPU to finish rendering
                iTracker.EndGPUQuery();
                // Signal the iWear VR920 that the image for the right eye is ready
                iTracker.SynchronizeEye(Eyes.RIGHT_EYE, graphics);
            }
            //not sure what to do for an else...

            base.Draw(gameTime);
        }
        protected void RenderScene(GameTime gameTime, Eyes Eye)
        {
            camera.CurrentEye = Eye;
            SetViewPortFor(Eye);
            currentScene.Render(GraphicsDevice, camera, simpleColorEffect);
        }
        private void SetViewPortFor(Eyes Eye)
        {
            if (Eye == Eyes.LEFT_EYE)
            {
                graphics.GraphicsDevice.Viewport = leftVP;
            }
            else if (Eye == Eyes.RIGHT_EYE)
            {
                graphics.GraphicsDevice.Viewport = rightVP;
            }
        }
        private void changeScene(IScene nextScene)
        {
            //stub...
            //currentScene dispose
            //currentscene = nextscene
            //currentscene initialise
        }

        /// <summary>
        /// iWear: On exiting we need to reset the iWears Video mode.
        /// </summary>
        protected override void OnExiting(Object sender, EventArgs args)
        {
            iTracker.Dispose();
        }
    }
}
